package com.yearno.baseframe.base

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.JsonParseException
import com.yearno.baseframe.http.BaseApiException
import com.yearno.baseframe.bean.BaseLoadingBean
import kotlinx.coroutines.*
import retrofit2.HttpException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

typealias Block<T> = suspend () -> T
typealias Error = suspend (e: Exception) -> Unit
typealias Cancel = suspend (e: Exception) -> Unit
typealias Complete = suspend () -> Unit

const val RESPONSE_CODE_TOKEN_EXPIRED = "20006"//token过期
const val RESPONSE_CODE_OTHER_LOGGED = "20007"//该账号已在其他设备登录
const val RESPONSE_CODE_VALIDATION_FAILED = "20008"//非法请求：身份验证失败请重新登录
const val RESPONSE_CODE_SYSTEM_MAINTENANCE = "9000"//系统维护错误码

open class BaseViewModel : ViewModel() {

    val pagerError: MutableLiveData<Exception> by lazy {
        MutableLiveData<Exception>()
    }

    val mLoadingLD = MutableLiveData<BaseLoadingBean>()

    private val mToastLD = MutableLiveData<String>()



    /**
     * 创建并执行协程
     * @param block 协程中执行
     * @param error 错误时执行
     * @param cancel 取消时只需
     * @param showErrorToast 是否弹出错误吐司
     * @return Job
     */
    protected fun launch(
            block: Block<Unit>,
            error: Error? = null,
            cancel: Cancel? = null,
            complete: Complete? = null,
            showErrorToast: Boolean = true,
            showLoading: Boolean = false
    ): Job {
        return viewModelScope.launch {
            try {
                mLoadingLD.value = BaseLoadingBean(true)
                block.invoke()
            } catch (e: Exception) {
                when (e) {
                    is CancellationException -> {
                        cancel?.invoke(e)
                    }
                    else -> {
                        onError(e, showErrorToast)
                        error?.invoke(e)
                    }
                }
            } finally {
                complete?.invoke()
                if (mLoadingLD.value?.isShow == true) {
                    mLoadingLD.value = BaseLoadingBean(false)
                }
            }
        }
    }

    /**
     * 创建并执行协程
     * @param block 协程中执行
     * @return Deferred<T>
     */
    protected fun <T> async(block: Block<T>): Deferred<T> {
        return viewModelScope.async { block.invoke() }
    }

    /**
     * 取消协程
     * @param job 协程job
     */
    protected fun cancelJob(job: Job?) {
        if (job != null && job.isActive && !job.isCompleted && !job.isCancelled) {
            job.cancel()
        }
    }

    /**
     * 统一处理错误
     * @param e 异常
     * @param showErrorToast 是否显示错误吐司
     */
    protected fun onError(e: Exception, showErrorToast: Boolean): Boolean {
        when (e) {
            is BaseApiException -> {
                when (e.code) {
                    RESPONSE_CODE_TOKEN_EXPIRED -> {//token过期
                        return true
                    }
                    RESPONSE_CODE_OTHER_LOGGED -> {//该账号已在其他设备登录
                        return true
                    }
                    RESPONSE_CODE_VALIDATION_FAILED -> {//非法请求：身份验证失败请重新登录
                        return true
                    }
                    RESPONSE_CODE_SYSTEM_MAINTENANCE -> {//系统维护
                        //移至HttpClientManager.responseInterceptor中统一处理
                        //AppRouter.routeSysMaintenanceActivity()
                        return true
                    }
                }
                if (showErrorToast) mToastLD.value = e.message
            }
            // 网络请求失败
            is ConnectException, is SocketTimeoutException, is UnknownHostException, is HttpException ->
                if (showErrorToast) mToastLD.value = "网络请求失败"
            // 数据解析错误
            is JsonParseException ->
                if (showErrorToast) mToastLD.value = "数据解析错误"
            // 其他错误
            else ->
                if (showErrorToast) mToastLD.value = e.message
        }
        return false
    }
}